package com.rainbow.chat.ws;

import com.alibaba.fastjson.JSONObject;
import com.rainbow.chat.cache.WbRedisClientTemplate;
import com.rainbow.chat.common.ChatMsg;
import com.rainbow.chat.common.ChatOnline;
import com.rainbow.chat.common.ChatUtil;
import com.rainbow.chat.entity.InfoChatMsg;
import com.rainbow.chat.enums.ChatMsgType;
import com.rainbow.chat.is8n.LANG;
import com.rainbow.chat.web.service.ChatMsgService;
import com.rainbow.constant.CacheConstants;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.util.MultiValueMap;
import org.yeauty.annotation.*;
import org.yeauty.pojo.Session;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * webSecoket 服务
 * @Author rainbow
 *  eg: test/java/chat.html
 */
@ServerEndpoint(host="${ws.host}",port = "${ws.port}",path = "${ws.path}"
        ,readerIdleTimeSeconds="${ws.idle.read}"
        ,writerIdleTimeSeconds="${ws.idle.write}"
        ,allIdleTimeSeconds="${ws.idle.all}"
)
public class WsServer {
    // 连接的channel
    private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    private static ChannelGroup channel1 = new DefaultChannelGroup("chatRoom1",GlobalEventExecutor.INSTANCE);
    private static ChannelGroup channel2 = new DefaultChannelGroup("chatRoom2",GlobalEventExecutor.INSTANCE);
    private static final AttributeKey<Integer> channelUid = AttributeKey.valueOf("uid");
    // 登陆用户 暂支持每人 一个设备登陆 最多1000同时在线 , 启动时查询群组信息
    private static Map<Integer, ChatOnline> onlineList = new ConcurrentHashMap(1000);
    private static final String START = "start";
    private static final String SUBPROTOCOL  = "stomp";
    @Autowired
    private ChatMsgService chatMsgService;
    // 聊天室1/2 在线的聊天室用户
    private static Map<Integer, ChannelGroup> chatRooms = new ConcurrentHashMap(3){{
        put(1, channel1);
        put(2, channel2);
    }};

    @Autowired
    private WbRedisClientTemplate<String,String> redis;

    @BeforeHandshake
    public void handshake(Session session, HttpHeaders headers
            , @RequestParam MultiValueMap reqMap, @PathVariable Map pathMap
//            , @RequestParam String req, @PathVariable Integer uid,@PathVariable String sid
    ){
        session.setSubprotocols(SUBPROTOCOL);
//        SocketAddress addr = session.channel().remoteAddress();
//        if (!"ok".equals(req)){
//            System.out.println("Authentication failed!");
//            session.close();
//        }
    }

    @OnOpen
    public void onOpen(Session session, HttpHeaders headers
            ,@RequestParam MultiValueMap reqMap, @PathVariable Map pathMap
//            , @RequestParam String req, @PathVariable Integer uid,@PathVariable String sid
    ){
        // 连接打开时间
        session.setAttribute(START, LocalDateTime.now());
        channels.add(session.channel());
        System.out.println(LANG.NEW_CONNECT);
    }

    @OnClose
    public void onClose(Session session) throws IOException {
        // TODO 是否需要主动 close
        afterClose(session.channel(),"下线了",true);
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        throwable.printStackTrace();
    }

    @OnMessage
    public void onMessage(Session session, String message) {
        System.out.println(LANG.SYMBLE_LM + session.channel().remoteAddress() + " 说：]" + message);
        try{
            message = message!=null ? message.trim() : "";
            int len = message.length();
            if(len>0 && len<=512){
                ChatMsg chatMsg = JSONObject.parseObject(message,ChatMsg.class);
                handleMsg(session,chatMsg);
            }else{
                session.channel().writeAndFlush(ChatUtil.getErrMsg(LANG.ERROR_FORMAT + len));
            }
        }catch (Exception e){
            e.printStackTrace();
            session.sendText(LANG.ERROR_FORMAT);
        }
    }

    @OnBinary
    public void onBinary(Session session, byte[] bytes) {
        session.sendText(LANG.ERROR_NOT_SUPPORT);
//        for (byte b : bytes) {
//            System.out.println(b);
//        }
//        session.sendBinary(bytes);
    }

    @OnEvent
    public void onEvent(Session session, Object evt) {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            switch (e.state()){
                case READER_IDLE:
                    // TODO 长时间离线 通知...
//                    session.close();break;
                case WRITER_IDLE:
                case ALL_IDLE:
                    session.channel().writeAndFlush(ChatUtil.getTipMsg(LANG.PING));break;
                default:
                    break;
            }
        }
    }

    private void afterClose(Channel channel,String msg,Boolean remove){
        System.out.println(LANG.SYMBLE_LM + channel.remoteAddress() + "]离线 : " + msg);
        Integer uid = channel.attr(channelUid).get();
        if(null!= uid){
            ChatOnline chatOnline = onlineList.get(uid);
            onlineList.remove(uid);
            if(chatOnline!=null){
                Integer groupId = chatOnline.getGroup();
                if(chatRooms.containsKey(groupId)){
                    // 退群
                    ChannelGroup chatRoom = chatRooms.get(groupId);
                    chatOnline.setGroup(0);
                    chatRoom.remove(channel);
                    chatRoom.writeAndFlush(ChatUtil.getTipMsg(uid + msg, ChatMsgType.TIP_OFFLINE));
                }
            }
        }
        if(remove){
            channels.remove(channel);
        }
    }
    // 消息处理
    private void handleMsg(Session session, ChatMsg chatMsg) {
        Channel channel = session.channel();
        String      msg       = chatMsg.getMsg();
        Integer     toUid     = chatMsg.getToUid();
        ChatMsgType type      = chatMsg.getType();
        Integer     fromUid   = chatMsg.getFromUid();
        Integer     groupId   = chatMsg.getGroupId();
        ChatMsgType typeSmall = chatMsg.getTypeSmall();
        if(ChatMsgType.ACTION.equals(type)) {
            if(ChatMsgType.ACTION_INIT.equals(typeSmall)) { // init
                // 检查fromUid sid[msg] 是否在线
                try {
                    if (!msg.equals(redis.get(CacheConstants.USER_SESSION + fromUid))) {
                        throw new Exception(LANG.ERROR_LOGIN +LANG.SYMBLE_M+ LANG.ERROR_REFRESH);
                    }
                } catch (Exception e) {
                    channel.writeAndFlush(ChatUtil.getErrMsg(e.getMessage()));
                    return;
                }
                if(onlineList.containsKey(fromUid)){ //已经init
                    if(msg.equals(onlineList.get(fromUid).getSid())){ // old sid
                        channel.writeAndFlush(ChatUtil.getTipMsg(LANG.TIP_LOGIN_FAIL_DEVICE));
                        return;
                    }else{ //new sid
                        // 下线原 channel
                        Channel tmp = channels.find(onlineList.get(fromUid).getChannelId());
                        tmp.writeAndFlush(ChatUtil.getErrMsg(LANG.TIP_LOGIN_OK_DEVICE));
                        tmp.close();
                    }
                }
                // init 1/2 : uid 写入channel
                channel.attr(channelUid).set(fromUid);
                ChatOnline chatOnline = new ChatOnline();
                chatOnline.setChannelId(channel.id());
                // init 2/2 : channel 写入onlineList
                onlineList.put(fromUid, chatOnline);
                channel.writeAndFlush(ChatUtil.getTipMsg(LANG.SUCCESS,ChatMsgType.TIP_INIT_OK));
            }else if(ChatMsgType.ACTION_GROUP_QUIT.equals(typeSmall)){ //退出群聊
                if(!isInit(fromUid,channel)){ return; }
                if(chatRooms.containsKey(groupId)) {
                    ChannelGroup chatRoom = chatRooms.get(groupId);
                    chatRoom.remove(channel);
                    chatRoom.writeAndFlush(ChatUtil.getTipMsg(fromUid + LANG.OUT_GROUP + groupId));
                }else{
                    channel.writeAndFlush(ChatUtil.getTipMsg(LANG.OUT_GROUP+ groupId+ LANG.FAIL));
                }
            }else if(ChatMsgType.ACTION_GROUP_ADD.equals(typeSmall)){ //加入群聊
                if(!isInit(fromUid,channel)){ return; }
                if(chatRooms.containsKey(groupId)) {
                    ChannelGroup chatRoom = chatRooms.get(groupId);
                    Integer oldGroup = onlineList.get(fromUid).getGroup();
                    if(!chatRoom.contains(channel)){
                        // 加入群聊
                        chatRoom.add(channel);
                        chatRoom.writeAndFlush(ChatUtil.getTipMsg(fromUid + LANG.IN_GROUP + groupId));
                        onlineList.get(fromUid).setGroup(groupId);
                        // 退出上次的群组
                        if(!groupId.equals(oldGroup) && (chatRooms.containsKey(oldGroup))){
                            ChannelGroup oldRoom = chatRooms.get(oldGroup);
                            oldRoom.remove(channel);
                            oldRoom.writeAndFlush(ChatUtil.getTipMsg(fromUid + LANG.OUT_GROUP + groupId));
                        }
                    }
                }else{
                    channel.writeAndFlush(ChatUtil.getErrMsg(LANG.IN_GROUP+ groupId+ LANG.FAIL + LANG.SYMBLE_M + LANG.ERROR_REFRESH));
                }
            }else{
                if(!isInit(fromUid,channel)){ return; }
                channel.writeAndFlush(ChatUtil.getErrMsg(LANG.ERROR_NOT_SUPPORT));
            }
        }else if(ChatMsgType.BOARDCAST.equals(type)){ // 公告
            if(fromUid.intValue() != channel.attr(channelUid).get()){
                channel.writeAndFlush(ChatUtil.getErrMsg(LANG.ERROR_INVALID));
            }else{
                channels.writeAndFlush(ChatUtil.getBoardMsg(groupId,msg));
            }
        }else if(ChatMsgType.CHAT.equals(type)){ // 私聊/群聊
            if(!isInit(fromUid,channel)){ return; }
            if(!isOwnner(channel,fromUid)){ return; }
            // 客户端自行判断是否为自己发的
            // 消息写入数据库表
            InfoChatMsg infoChatMsg = new InfoChatMsg();
            infoChatMsg.setFromUid(fromUid);
            infoChatMsg.setGroupId(groupId);
            infoChatMsg.setToUid(toUid);
            infoChatMsg.setType(type);
            infoChatMsg.setMsg(getFilterMsg(msg));
            infoChatMsg.setChatIndex(getChatIndex(fromUid,toUid,groupId));
            infoChatMsg.setTypeSmall(chatMsg.getTypeSmall());
            infoChatMsg.setTime(chatMsg.getTime());
            infoChatMsg.setMsgId(chatMsg.getMsgId());
            if(groupId>0){ // 群聊
                if(chatRooms.containsKey(groupId)){
                    if(chatRooms.get(groupId).contains(channel)){
                        chatRooms.get(groupId).writeAndFlush(ChatUtil.getGroupMsg(fromUid,groupId,msg))
                            .addListener(f-> handleFutrue(f,channel,infoChatMsg));
                    }else{
                        channel.writeAndFlush(ChatUtil.getErrMsg(LANG.ERROR_NEED_IN_GROUP));
                    }
                }else{
                    channel.writeAndFlush(ChatUtil.getErrMsg(LANG.ERROR_GROUP));
                }
            }else{ //私聊
                if(toUid>0){
                    if(onlineList.containsKey(toUid)){ //在线
                        channels.find(onlineList.get(toUid).getChannelId())
                                .writeAndFlush(ChatUtil.getChatMsg(fromUid,toUid,msg))
                                .addListener(f -> handleFutrue(f,channel,infoChatMsg));
                    }else{
                        channel.writeAndFlush(ChatUtil.getTipMsg(LANG.USER_OFFLINE));
                    }
                }else{
                    channel.writeAndFlush(ChatUtil.getTipMsg(LANG.USER_OFFLINE));
                }
            }
        }else if(ChatMsgType.TIP.equals(type)){ // tip
            if(LANG.PING.equalsIgnoreCase(msg)){
                channel.writeAndFlush(ChatUtil.getMsg(chatMsg));
            }
        }else{
            channel.writeAndFlush(ChatUtil.getErrMsg(LANG.ERROR_NOT_SUPPORT));
        }
    }

    private void handleFutrue(Future f,Channel channel,InfoChatMsg infoChatMsg){
        if(f.isDone()){
            if(f.isSuccess()){
                chatMsgService.add(infoChatMsg);
                channel.writeAndFlush(ChatUtil.getTipMsg(LANG.SUCCESS,
                        ChatMsgType.TIP_PUSH_OK));
            }else if(f.isCancelled() || f.cause()!=null){
                channel.writeAndFlush(ChatUtil.getTipMsg(infoChatMsg.getMsgId(),ChatMsgType.TIP_PUSH_FAIL));
            }
        }
    }

    private String getFilterMsg(String msg) {
        // TODO 内容过滤
        return msg;
    }

    private String getChatIndex(
            @NonNull Integer fromUid,@NonNull  Integer toUid,@NonNull  Integer groupId
    ) {
        if(groupId!=null && groupId>0){
            return groupId.toString();
        }else{
            if(fromUid!=null){ fromUid = 0; }
            if(toUid!=null){  toUid = 0; }
            return fromUid>toUid ? toUid + LANG.SYMBLE_U + fromUid : fromUid+LANG.SYMBLE_U+toUid;
        }
    }

    // 检查uid 是否init
    private boolean isInit(int uid, Channel channel){
        if(!onlineList.containsKey(uid)){
            channel.writeAndFlush(ChatUtil.getTipMsg(LANG.NEED_INIT,ChatMsgType.TIP_NEED_INIT));
            return false;
        }else{
            return true;
        }
    }
    // 检查 channel拥有者
    private boolean isOwnner(Channel channel, Integer fromUid) {
        if(fromUid.intValue() != channel.attr(channelUid).get()){
            channel.writeAndFlush(ChatUtil.getErrMsg(LANG.ERROR_INVALID));
            return false;
        }
        return true;
    }

    /**
     * 发送广播
     * @param msg
     */
    public void pushAll(String msg){
        channels.writeAndFlush(ChatUtil.getTipMsg(msg));
    }

    /**
     * 发送群聊
     * @param group
     * @param msg
     */
    public void pushGroup(Integer group,String msg){
        chatRooms.get(group).writeAndFlush(ChatUtil.getGroupMsg(ChatUtil.SYSTEM,group,msg));
    }

    /**
     * 发送私聊
     * @param uid
     * @param msg
     */
    public void pushUser(Integer uid,String msg){
        channels.find(onlineList.get(uid).getChannelId()).writeAndFlush(ChatUtil.getChatMsg(0,uid,msg));
    }

    /**
     * 统计
     */
    public String getCount(){
        return onlineList.size()
                +LANG.SYMBLE_L + channels.size() + ": room[" + chatRooms.get(1).size()
                +LANG.SYMBLE_L + chatRooms.get(2).size();
    }

    /**
     * 统计
     */
    public Map<Integer,ChatOnline> queryOnline(){
        return onlineList;
    }

    /**
     * 统计
     */
    public void offline(Integer uid){
        channels.find(onlineList.get(uid).getChannelId()).close();
    }
}